/**
 * 
 */
package org.statewalker.fsm.providers;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
 * @author kotelnikov
 */
public abstract class FsmHierarchicalStateProvider<SK, S, EK, E>
    extends
    AbstractFsmStateProvider<SK, S, EK, E> {

    private Map<SK, FsmStateDescriptor<SK, EK>> fDescriptors = new HashMap<SK, FsmStateDescriptor<SK, EK>>();

    private Stack<FsmStateDescriptor<SK, EK>> fDescriptorStack = new Stack<FsmStateDescriptor<SK, EK>>();

    /**
     * 
     */
    public FsmHierarchicalStateProvider(
        SK initialStateKey,
        SK finalStateKey,
        EK defaultEventKey) {
        super(initialStateKey, finalStateKey, defaultEventKey);
    }

    public void beginState(SK stateKey) {
        beginState(stateKey, new HashMap<String, Object>());
    }

    public void beginState(SK stateKey, Map<String, ?> params) {
        FsmStateDescriptor<SK, EK> descriptor = newDescriptor(stateKey, params);
        FsmStateDescriptor<SK, EK> parentDescriptor = !fDescriptorStack.empty()
            ? fDescriptorStack.peek()
            : null;
        if (parentDescriptor != null) {
            parentDescriptor.addSubstateDescriptor(stateKey, descriptor);
        } else {
            setRootDescriptor(stateKey, descriptor);
        }
        fDescriptorStack.push(descriptor);
    }

    public void endState() {
        if (!fDescriptorStack.empty())
            fDescriptorStack.pop();
    }

    /**
     * @see org.statewalker.fsm.providers.AbstractFsmStateProvider#getNextSubstateKey(java.lang.Object,
     *      java.lang.Object, java.lang.Object)
     */
    @Override
    protected SK getNextSubstateKey(S parent, SK stateKey, EK eventKey) {
        FsmStateDescriptor<SK, EK> parentDescriptor = getStateDescriptor(parent);
        SK targetKey = parentDescriptor.getNextSubstateKey(stateKey, eventKey);
        return targetKey;
    }

    protected abstract E getParentEvent(E event);

    /**
     * @param key the key of the root state to return
     * @return a root state descriptor corresponding to the specified state key
     */
    public FsmStateDescriptor<SK, EK> getRootStateDescriptor(SK key) {
        return fDescriptors.get(key);
    }

    /**
     * Returns a state descriptor associated with the given state. This method
     * should be overloaded in subclasses.
     * 
     * @param state the state for which the corresponding state descriptor
     *        should be returned.
     * @return a state descriptor associated with the specified state.
     */
    protected abstract FsmStateDescriptor<SK, EK> getStateDescriptor(S state);

    @Override
    protected abstract SK getStateKey(S state);

    protected FsmStateDescriptor<SK, EK> newDescriptor(
        SK stateKey,
        Map<String, ?> params) {
        boolean root = fDescriptorStack.isEmpty();
        return new FsmStateDescriptor<SK, EK>(
            root,
            fInitialStateKey,
            fFinalStateKey);
    }

    /**
     * @see org.statewalker.fsm.providers.AbstractFsmStateProvider#newState(java.util.List,
     *      java.lang.Object)
     */
    @Override
    protected S newState(List<S> stack, SK targetKey) {
        FsmStateDescriptor<SK, EK> descriptor = null;
        for (int i = stack.size() - 1; descriptor == null && i >= 0; i--) {
            S parent = stack.get(i);
            FsmStateDescriptor<SK, EK> parentDescriptor = getStateDescriptor(parent);
            descriptor = parentDescriptor.getSubstateDescriptor(targetKey);
        }
        if (descriptor == null)
            return null;
        S parent = stack.get(stack.size() - 1);
        return newState(parent, targetKey, descriptor);
    }

    protected abstract S newState(
        S parent,
        SK state,
        FsmStateDescriptor<SK, EK> descriptor);

    public void onTransition(SK from, EK event, SK to) {
        FsmStateDescriptor<SK, EK> parentDescriptor = !fDescriptorStack.empty()
            ? fDescriptorStack.peek()
            : null;
        parentDescriptor.addTransition(from, event, to);
    }

    protected void setRootDescriptor(
        SK stateKey,
        FsmStateDescriptor<SK, EK> descriptor) {
        fDescriptors.put(stateKey, descriptor);
    }
}
